/*
 *	This file is part of Transdroid <http://www.transdroid.org>
 *
 *	Transdroid is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	Transdroid is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with Transdroid.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.transdroid.daemon.adapters.bitComet;

import com.android.internal.http.multipart.MultipartEntity;
import com.android.internal.http.multipart.Part;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.HttpHelper;
import org.transdroid.multipart.BitCometFilePart;
import org.transdroid.multipart.Utf8StringPart;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * The daemon adapter for the BitComet torrent client.
 *
 * @author SeNS (sensboston)
 * <p/>
 * 09/26/2012: added AJAX support for BitComet v.1.34 and up : added additional tasks support
 */
public class BitCometAdapter implements IDaemonAdapter {

    private static final String LOG_NAME = "BitComet daemon";

    private DaemonSettings settings;
    private DefaultHttpClient httpclient;

    public BitCometAdapter(DaemonSettings settings) {
        this.settings = settings;
    }

    /**
     * Returns the size of the torrent, as parsed form some string
     *
     * @param size The size in a string format, i.e. '691 MB'
     * @return The size in bytes
     */
    private static long convertSize(String size) {
        try {
            if (size.endsWith("GB")) {
                return (long) (Float.parseFloat(size.substring(0, size.indexOf("GB"))) * 1024 * 1024 * 1024);
            } else if (size.endsWith("MB")) {
                return (long) (Float.parseFloat(size.substring(0, size.indexOf("MB"))) * 1024 * 1024);
            } else if (size.endsWith("kB")) {
                return (long) (Float.parseFloat(size.substring(0, size.indexOf("kB"))) * 1024);
            } else if (size.endsWith("B")) {
                return (long) (Float.parseFloat(size.substring(0, size.indexOf("B"))));
            }
        } catch (Exception ignored) {
        }
        return 0;
    }

    /**
     * Returns the part done (or progress) of a torrent, as parsed from some string
     *
     * @param progress The part done in a string format, i.e. '15.96'
     * @return The part done as [0..1] fraction, i.e. 0.1596
     */
    public static float convertProgress(String progress) {
        return Float.parseFloat(progress) / 1000.0f;
    }

    @Override
    public DaemonTaskResult executeTask(Log log, DaemonTask task) {

        try {
            switch (task.getMethod()) {
                case Retrieve:

                    // Request all torrents from server
                    // first, check client for the new AJAX interface (BitComet v.1.34 and up)
                    try {
                        String xmlResult = makeRequest(log, "/panel/task_list_xml");
                        if (xmlResult.startsWith("<?xml")) {
                            return new RetrieveTaskSuccessResult((RetrieveTask) task, parseXmlTorrents(xmlResult),
                                    null);
                        }
                    } catch (Exception e) {
                        // it's probably an old client, parse HTML instead
                        String htmlResult = makeRequest(log, "/panel/task_list");
                        return new RetrieveTaskSuccessResult((RetrieveTask) task, parseHttpTorrents(log, htmlResult),
                                null);
                    }

                case GetFileList:

                    // Request files listing for a specific torrent
                    String fhash = task.getTargetTorrent().getUniqueID();
                    String fileListResult = makeRequest(log, "/panel/task_detail", new BasicNameValuePair("id", fhash),
                            new BasicNameValuePair("show", "files"));
                    return new GetFileListTaskSuccessResult((GetFileListTask) task,
                            parseHttpTorrentFiles(fileListResult, fhash));

                case AddByFile:

                    // Upload a local .torrent file
                    String ufile = ((AddByFileTask) task).getFile();
                    makeFileUploadRequest(log, "/panel/task_add_bt_result", ufile);
                    return new DaemonTaskSuccessResult(task);

                case AddByUrl:

                    // Request to add a torrent by URL
                    String url = ((AddByUrlTask) task).getUrl();
                    makeUploadUrlRequest(log, "/panel/task_add_httpftp_result", url);
                    return new DaemonTaskSuccessResult(task);

                case AddByMagnetUrl:

                    // Request to add a torrent by URL
                    String magnetUrl = ((AddByMagnetUrlTask) task).getUrl();
                    makeUploadUrlRequest(log, "/panel/task_add_httpftp_result", magnetUrl);
                    return new DaemonTaskSuccessResult(task);

                case Remove:

                    // Remove a torrent
                    RemoveTask removeTask = (RemoveTask) task;
                    makeRequest(log, "/panel/task_delete",
                            new BasicNameValuePair("id", removeTask.getTargetTorrent().getUniqueID()),
                            new BasicNameValuePair("action",
                                    (removeTask.includingData() ? "delete_all" : "delete_task")));
                    return new DaemonTaskSuccessResult(task);

                case Pause:

                    // Pause a torrent
                    makeRequest(log, "/panel/task_action",
                            new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()),
                            new BasicNameValuePair("action", "stop"));
                    return new DaemonTaskSuccessResult(task);

                case Resume:

                    // Resume a torrent
                    makeRequest(log, "/panel/task_action",
                            new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()),
                            new BasicNameValuePair("action", "start"));
                    return new DaemonTaskSuccessResult(task);

                case PauseAll:

                    // Suspend (pause) all active torrents
                    makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "suspend_all"));
                    return new DaemonTaskSuccessResult(task);

                case ResumeAll:

                    // Resume suspended torrents
                    makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "resume_all"));
                    return new DaemonTaskSuccessResult(task);

                case StopAll:

                    // Stop all torrents
                    makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "stop_all"));
                    return new DaemonTaskSuccessResult(task);

                case StartAll:

                    // Start all torrents for download and seeding
                    makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "start_all_download"));
                    makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "start_all_seeding"));

                    return new DaemonTaskSuccessResult(task);

                case SetTransferRates:

                    // Request to set the maximum transfer rates
                    SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
                    String dl =
                            Integer.toString((ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()));
                    String ul = Integer.toString((ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()));
                    makeRequest(log, "/panel/option_set", new BasicNameValuePair("key", "down_rate_max"),
                            new BasicNameValuePair("value", dl));
                    makeRequest(log, "/panel/option_set", new BasicNameValuePair("key", "up_rate_max"),
                            new BasicNameValuePair("value", ul));
                    return new DaemonTaskSuccessResult(task);

                default:
                    return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
                            task.getMethod() + " is not supported by " + getType()));
            }
        } catch (DaemonException e) {
            return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
        }
    }

    /**
     * Instantiates an HTTP client with proper credentials that can be used for all Buffalo NAS requests.
     *
     * @throws DaemonException On conflicting or missing settings
     */
    private void initialise() throws DaemonException {
        httpclient = HttpHelper.createStandardHttpClient(settings, true);
    }

    /**
     * Build the URL of the HTTP request from the user settings
     *
     * @return The URL to request
     */
    private String buildWebUIUrl(String path) {
        return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path;
    }

    private String makeRequest(Log log, String url, NameValuePair... params) throws DaemonException {

        try {

            // Initialize the HTTP client
            if (httpclient == null) {
                initialise();
            }

            // Add the parameters to the query string
            boolean first = true;
            StringBuilder urlBuilder = new StringBuilder(url);
            for (NameValuePair param : params) {
                if (first) {
                    urlBuilder.append("?");
                    first = false;
                } else {
                    urlBuilder.append("&");
                }
                urlBuilder.append(param.getName()).append("=").append(param.getValue());
            }
            url = urlBuilder.toString();

            // Make the request
            HttpResponse response = httpclient.execute(new HttpGet(buildWebUIUrl(url)));
            HttpEntity entity = response.getEntity();
            if (entity != null) {

                // Read HTTP response
                java.io.InputStream instream = entity.getContent();
                String result = HttpHelper.convertStreamToString(instream);
                instream.close();

                // Return raw result
                return result;
            }

            log.d(LOG_NAME, "Error: No entity in HTTP response");
            throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");

        } catch (UnsupportedEncodingException e) {
            throw new DaemonException(ExceptionType.ConnectionError, e.toString());
        } catch (Exception e) {
            log.d(LOG_NAME, "Error: " + e.toString());
            throw new DaemonException(ExceptionType.ConnectionError, e.toString());
        }

    }

    private boolean makeFileUploadRequest(Log log, String path, String file) throws DaemonException {

        try {

            // Initialize the HTTP client
            if (httpclient == null) {
                initialise();
            }

            // Get default download file location first
            HttpResponse response = httpclient.execute(new HttpGet(buildWebUIUrl("/panel/task_add_bt")));
            HttpEntity entity = response.getEntity();
            if (entity != null) {

                // Read BitComet response
                java.io.InputStream instream = entity.getContent();
                String result = HttpHelper.convertStreamToString(instream);
                instream.close();

                int idx = result.indexOf("save_path' value='") + 18;
                String defaultPath = result.substring(idx, result.indexOf("'>", idx));

                // Setup request using POST
                HttpPost httppost = new HttpPost(buildWebUIUrl(path));
                File upload = new File(URI.create(file));
                Part[] parts =
                        {new BitCometFilePart("torrent_file", upload), new Utf8StringPart("save_path", defaultPath)};
                httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));

                // Make the request
                response = httpclient.execute(httppost);

                entity = response.getEntity();
                if (entity != null) {
                    // Check BitComet response
                    instream = entity.getContent();
                    result = HttpHelper.convertStreamToString(instream);
                    instream.close();
                    if (result.indexOf("failed!") > 0) {
                        throw new Exception("Adding torrent file failed");
                    }
                }

                return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
            }
            return false;

        } catch (FileNotFoundException e) {
            throw new DaemonException(ExceptionType.FileAccessError, e.toString());
        } catch (Exception e) {
            log.d(LOG_NAME, "Error: " + e.toString());
            throw new DaemonException(ExceptionType.ConnectionError, e.toString());
        }
    }

    private boolean makeUploadUrlRequest(Log log, String path, String url) throws DaemonException {

        try {

            // Initialize the HTTP client
            if (httpclient == null) {
                initialise();
            }

            // Get default download file location first
            HttpResponse response = httpclient.execute(new HttpGet(buildWebUIUrl("/panel/task_add_httpftp")));
            HttpEntity entity = response.getEntity();
            if (entity != null) {

                // Read BitComet response
                java.io.InputStream instream = entity.getContent();
                String result = HttpHelper.convertStreamToString(instream);
                instream.close();

                int idx = result.indexOf("save_path' value='") + 18;
                String defaultPath = result.substring(idx, result.indexOf("'>", idx));

                // Setup form fields and post request
                HttpPost httppost = new HttpPost(buildWebUIUrl(path));

                List<NameValuePair> params = new ArrayList<>();
                params.add(new BasicNameValuePair("url", url));
                params.add(new BasicNameValuePair("save_path", defaultPath));
                params.add(new BasicNameValuePair("connection", "5"));
                params.add(new BasicNameValuePair("ReferPage", ""));
                params.add(new BasicNameValuePair("textSpeedLimit", "0"));
                httppost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

                // Make the request
                response = httpclient.execute(httppost);

                entity = response.getEntity();
                if (entity != null) {
                    // Check BitComet response
                    instream = entity.getContent();
                    result = HttpHelper.convertStreamToString(instream);
                    instream.close();
                    if (result.indexOf("failed!") > 0) {
                        throw new Exception("Adding URL failed");
                    }
                }

                return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
            }
            return false;
        } catch (Exception e) {
            log.d(LOG_NAME, "Error: " + e.toString());
            throw new DaemonException(ExceptionType.ConnectionError, e.toString());
        }
    }

    /**
     * Parse BitComet HTML page (http response)
     *
     * @param response The raw HTML response from the server
     * @return The parsed list of torrents from the raw HTML content
     * @throws DaemonException
     */
    private ArrayList<Torrent> parseHttpTorrents(Log log, String response) throws DaemonException {

        ArrayList<Torrent> torrents = new ArrayList<>();

        try {

            // Find, prepare and split substring with HTML tag TABLE
            String[] parts =
                    response.substring(response.indexOf("<TABLE"), response.indexOf("</TABLE>")).replaceAll("</td>", "")
                            .replaceAll("</tr>", "").replaceAll("\n", "").split("<tr>");

            for (int i = 2; i < parts.length; i++) {

                String[] subParts = parts[i].replaceAll("<td>", "<td").split("<td");

                if (subParts.length == 10 && subParts[1].contains("BT")) {

                    String name = subParts[2].substring(subParts[2].indexOf("/panel/task_detail"));
                    name = name.substring(name.indexOf(">") + 1, name.indexOf("<"));

                    TorrentStatus status = convertStatus(subParts[3]);
                    String percenDoneStr = subParts[6];
                    String downloadRateStr = subParts[7];
                    String uploadRateStr = subParts[8];

                    long size = convertSize(subParts[5]);
                    float percentDone = Float.parseFloat(percenDoneStr.substring(0, percenDoneStr.indexOf("%")));
                    long sizeDone = (long) (size * percentDone / 100);

                    int rateUp = 1000 * Integer.parseInt(uploadRateStr.substring(0, uploadRateStr.indexOf("kB/s")));
                    int rateDown =
                            1000 * Integer.parseInt(downloadRateStr.substring(0, downloadRateStr.indexOf("kB/s")));

                    // Unfortunately, there is no info for above values providing by BitComet now,
                    // so we may only send additional request for that
                    int leechers = 0;
                    int seeders = 0;
                    int knownLeechers = 0;
                    int knownSeeders = 0;
                    int distributed_copies = 0;
                    long sizeUp;
                    String comment;
                    Date dateAdded;

                    // Comment code below to speedup torrent listing
                    // P.S. feature request to extend torrents info is already sent to the BitComet developers
                    //*
                    // Lets make summary request and parse details
                    String summary = makeRequest(log, "/panel/task_detail", new BasicNameValuePair("id", "" + (i - 2)),
                            new BasicNameValuePair("show", "summary"));

                    String[] sumParts = summary.substring(summary.indexOf("<div align=\"left\">Value</div></th>"))
                            .split("<tr><td>");
                    comment = sumParts[7].substring(sumParts[7].indexOf("<td>") + 4, sumParts[7].indexOf("</td></tr>"));

                    // Indexes for date and uploaded size
                    int idx = 9;
                    int sizeIdx = 12;

                    if (status == TorrentStatus.Downloading) {
                        seeders = Integer.parseInt(sumParts[9]
                                .substring(sumParts[9].indexOf("Seeds:") + 6, sumParts[9].indexOf("(Max possible")));
                        leechers = Integer.parseInt(sumParts[9].substring(sumParts[9].indexOf("Peers:") + 6,
                                sumParts[9].lastIndexOf("(Max possible")));
                        knownSeeders = Integer.parseInt(sumParts[9]
                                .substring(sumParts[9].indexOf("(Max possible:") + 14, sumParts[9].indexOf(")")));
                        knownLeechers = Integer.parseInt(sumParts[9]
                                .substring(sumParts[9].lastIndexOf("(Max possible:") + 14,
                                        sumParts[9].lastIndexOf(")")));
                        idx = 13;
                        sizeIdx = 16;
                    }

                    DateFormat df = new SimpleDateFormat("yyyy-mm-dd kk:mm:ss");
                    dateAdded = df.parse(sumParts[idx]
                            .substring(sumParts[idx].indexOf("<td>") + 4, sumParts[idx].indexOf("</td></tr>")));
                    //sizeDone =  convertSize(sumParts[sizeIdx].substring(sumParts[sizeIdx].indexOf("<td>")+4, sumParts[sizeIdx].indexOf(" (")));
                    sizeUp = convertSize(sumParts[sizeIdx + 1]
                            .substring(sumParts[sizeIdx + 1].indexOf("<td>") + 4, sumParts[sizeIdx + 1].indexOf(" (")));
                    //*

                    // Add the parsed torrent to the list
                    // @formatter:off
                    torrents.add(new Torrent(
                            (long) i - 2,
                            null,
                            name,
                            status,
                            null,
                            rateDown,
                            rateUp,
                            seeders,
                            knownSeeders,
                            leechers,
                            knownLeechers,
                            (rateDown == 0 ? -1 : (int) ((size - sizeDone) / rateDown)),
                            sizeDone,
                            sizeUp,
                            size,
                            percentDone / 100,
                            distributed_copies,
                            comment,
                            dateAdded,
                            null,
                            null,
                            settings.getType()));
                    // @formatter:on
                }
            }
        } catch (Exception e) {
            throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response.");
        }

        return torrents;
    }

    /**
     * Parse BitComet AJAX response that code was copy-pasted and slightly modified from \Ktorrent\StatsParser.java
     *
     * @param response The raw XML data as string that was returned by the server
     * @return The parsed list of torrents from the XML
     * @throws DaemonException
     */
    private ArrayList<Torrent> parseXmlTorrents(String response) throws DaemonException {

        ArrayList<Torrent> torrents = new ArrayList<>();

        try {
            // Use a PullParser to handle XML tags one by one
            XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
            xpp.setInput(new StringReader(response));

            // Temp variables to load into torrent objects
            int id = 0;
            String name = "";
            @SuppressWarnings("unused") String hash = "";
            TorrentStatus status = TorrentStatus.Unknown;
            long sizeDone = 0;
            long sizeUp = 0;
            long totalSize = 0;
            int rateDown = 0;
            int rateUp = 0;
            int seeders = 0;
            int seedersTotal = 0;
            int leechers = 0;
            int leechersTotal = 0;
            float progress = 0;
            String label = "";
            Date dateAdded = new Date();

            // Start pulling
            int next = xpp.nextTag();
            String tagName = xpp.getName();

            while (next != XmlPullParser.END_DOCUMENT) {

                if (next == XmlPullParser.END_TAG && tagName.equals("task")) {

                    // End of a 'transfer' item, add gathered torrent data
                    sizeDone = (long) (totalSize * progress);
                    // @formatter:off
                    torrents.add(new Torrent(
                            id,
                            null, // hash,  // we suppose to use simple integer IDs
                            name,
                            status,
                            null,
                            rateDown,
                            rateUp,
                            seeders,
                            seedersTotal,
                            leechers,
                            leechersTotal,
                            (int) ((status == TorrentStatus.Downloading && rateDown != 0) ? (totalSize - sizeDone) / rateDown : -1), // eta (in seconds) = (total_size_in_btes - bytes_already_downloaded) / bytes_per_second
                            sizeDone,
                            sizeUp,
                            totalSize,
                            progress,
                            0f,
                            label,
                            dateAdded,
                            null,
                            null, // Not supported in the web interface
                            settings.getType()));
                    // @formatter:on

                } else if (next == XmlPullParser.START_TAG && tagName.equals("task")) {

                    // Start of a new 'transfer' item; reset gathered torrent data
                    name = "";
                    //hash = "";
                    status = TorrentStatus.Unknown;
                    sizeDone = 0;
                    sizeUp = 0;
                    totalSize = 0;
                    rateDown = 0;
                    rateUp = 0;
                    seeders = 0;
                    seedersTotal = 0;
                    leechers = 0;
                    leechersTotal = 0;
                    progress = 0;
                    label = "";
                    dateAdded = new Date();

                } else if (next == XmlPullParser.START_TAG) {

                    // Probably encountered a torrent property, i.e. '<type>BT</type>'
                    next = xpp.next();
                    if (next == XmlPullParser.TEXT) {
                        switch (tagName) {
                            case "name":
                                name = xpp.getText().trim();
                                break;
                            case "id":
                                id = Integer.parseInt(xpp.getText().trim());
                                break;
                            case "infohash":
                                hash = xpp.getText().trim();
                                break;
                            case "state":
                                status = convertStatus(xpp.getText());
                                break;
                            case "bytes_downloaded":
                                sizeDone = Integer.parseInt(xpp.getText());
                                break;
                            case "bytes_uploaded":
                                sizeUp = Integer.parseInt(xpp.getText());
                                break;
                            case "size":
                                totalSize = Long.parseLong(xpp.getText());
                                break;
                            case "down_speed":
                                rateDown = Integer.parseInt(xpp.getText());
                                break;
                            case "up_speed":
                                rateUp = Integer.parseInt(xpp.getText());
                                break;
                            case "seeders":
                                seeders = Integer.parseInt(xpp.getText());
                                break;
                            case "total_seeders":
                                seedersTotal = Integer.parseInt(xpp.getText());
                                break;
                            case "peers":
                                leechers = Integer.parseInt(xpp.getText());
                                break;
                            case "total_peers":
                                leechersTotal = Integer.parseInt(xpp.getText());
                                break;
                            case "progress_permillage":
                                progress = convertProgress(xpp.getText());
                                break;
                            case "created_time":
                                dateAdded = new Date(Long.parseLong(xpp.getText()));
                                break;
                            case "comment":
                                label = xpp.getText().trim();
                                break;
                        }
                    }
                }

                next = xpp.next();
                if (next == XmlPullParser.START_TAG || next == XmlPullParser.END_TAG) {
                    tagName = xpp.getName();
                }
            }

        } catch (XmlPullParserException e) {
            throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
        } catch (Exception e) {
            throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response.");
        }

        return torrents;
    }

    /**
     * Parse BitComet HTML page (HTTP response)
     *
     * @param response The raw HTML response from the server
     * @return The parsed list of files in the torrent from the raw HTML
     * @throws DaemonException
     */
    private ArrayList<TorrentFile> parseHttpTorrentFiles(String response, String hash) throws DaemonException {

        // Parse response
        ArrayList<TorrentFile> torrentfiles = new ArrayList<>();

        try {

            String[] files = response.substring(response.indexOf("Operation Method</div></th>") + 27,
                    response.lastIndexOf("</TABLE>")).replaceAll("</td>", "").replaceAll("</tr>", "").split("<tr>");

            for (int i = 1; i < files.length; i++) {

                String[] fileDetails = files[i].replace(">", "").split("<td");

                long size = convertSize(fileDetails[4].substring(fileDetails[4].indexOf("&nbsp&nbsp ") + 11));
                long sizeDone = 0;
                if (!fileDetails[2].contains("--")) {
                    double percentDone = Double.parseDouble(fileDetails[2].substring(0, fileDetails[2].indexOf("%")));
                    sizeDone = (long) (size / 100.0 * percentDone);
                }

                // @formatter:off
                torrentfiles.add(new TorrentFile(
                        hash,
                        fileDetails[3],
                        fileDetails[3],
                        settings.getDownloadDir() + fileDetails[3],
                        size,
                        sizeDone,
                        convertPriority(fileDetails[1])));
                // @formatter:on
            }
        } catch (Exception e) {
            throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response.");
        }

        // Return the list
        return torrentfiles;
    }

    /**
     * Parse BitComet torrent files priority
     */
    private Priority convertPriority(String priority) {
        if (priority.equals("Very High") || priority.equals("High")) {
            return Priority.High;
        } else if (priority.equals("Normal")) {
            return Priority.Normal;
        }
        return Priority.Off;
    }

    /**
     * Parse BitComet torrent status
     */
    private TorrentStatus convertStatus(String state) {
        // Status is given as a descriptive string and an indication if the torrent was stopped/paused
        if (state.equals("stopped")) {
            return TorrentStatus.Paused;
        } else if (state.equals("running")) {
            return TorrentStatus.Downloading;
        }
        return TorrentStatus.Unknown;
    }

    @Override
    public Daemon getType() {
        return settings.getType();
    }

    @Override
    public DaemonSettings getSettings() {
        return this.settings;
    }

}
